// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Table.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework
{
    #region Usings

    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Org.OpenTrader.Framework.LiveObjects;

    #endregion

    /// <summary>
    /// The table.
    /// </summary>
    [Serializable]
    public class Table
    {
        #region Constants and Fields

        /// <summary>
        /// Column list
        /// </summary>
        private readonly IList<Column> Columns = new List<Column>();

        #endregion

        #region Properties

        /// <summary>
        /// Number of Columns
        /// </summary>
        public int ColCount
        {
            get
            {
                return this.Columns.Count;
            }
        }

        /// <summary>
        /// Gets ColumnNames.
        /// </summary>
        public string[] ColumnNames
        {
            get
            {
                IList<string> columns = new List<string>();
                foreach (var column in this.Columns)
                {
                    columns.Add(column.Name);
                }

                return columns.ToArray();
            }
        }

        /// <summary>
        /// Number of Rows
        /// </summary>
        public int RowCount
        {
            get
            {
                if (this.Columns.Count <= 0)
                {
                    return 0;
                }

                return this.Columns[0].Nb;
            }
        }

        #endregion

        #region Indexers

        /// <summary>
        /// The this.
        /// </summary>
        /// <param name="columnIndex">
        /// The columnIndex.
        /// </param>
        public Column this[int columnIndex]
        {
            get
            {
                if (columnIndex >= 0 && columnIndex < this.ColCount)
                {
                    return this.Columns[columnIndex];
                }

                return null;
            }
        }

        /// <summary>
        /// The this.
        /// </summary>
        /// <param name="rowIndex">
        /// The row.
        /// </param>
        /// <param name="columnIndex">
        /// The column.
        /// </param>
        /// <returns>
        /// </returns>
        public Cell this[int rowIndex, int columnIndex]
        {
            get
            {
                var column = this[columnIndex];
                return column == null ? null : column[rowIndex];
            }
        }

        /// <summary>
        /// The this.
        /// </summary>
        /// <param name="rowIndex">
        /// The row index.
        /// </param>
        /// <param name="columnName">
        /// The column name.
        /// </param>
        public Cell this[int rowIndex, string columnName]
        {
            get
            {
                var colIndex = this.ColumnIndex(columnName);
                if (colIndex < 0)
                {
                    return null;
                }

                var column = this[colIndex];
                return column == null ? null : column[rowIndex];
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Add a column
        /// </summary>
        /// <param name="c">
        /// not null
        /// </param>
        /// <returns>
        /// The add.
        /// </returns>
        public bool Add(Column c)
        {
            // check column
            if (c == null)
            {
                return true;
            }

            if (this.ColCount == 0)
            {
                this.Columns.Add(c);
                return true;
            }

            // check nbrow
            if (this.RowCount != c.Nb)
            {
                return false;
            }

            // no duplicate column
            var index = this.ColumnIndex(c.Name);
            if (index != -1)
            {
                return false;
            }

            // add the column
            this.Columns.Add(c);
            return true;
        }

        /// <summary>
        /// Add a column
        /// </summary>
        /// <param name="name">
        /// </param>
        /// <returns>
        /// The add.
        /// </returns>
        public bool Add(string name)
        {
            var c = new Column(name);
            return this.Add(c);
        }

        /// <summary>
        /// Clear all columns
        /// </summary>
        public void Clear()
        {
            this.Columns.Clear();
        }

        /// <summary>
        /// Returns the index of the column having the name equals to name, -1 if not found
        /// </summary>
        /// <param name="name">
        /// </param>
        /// <returns>
        /// The column index.
        /// </returns>
        public int ColumnIndex(string name)
        {
            var i = 0;
            foreach (var c in this.Columns)
            {
                if (c != null)
                {
                    if (c.Name == name)
                    {
                        return i;
                    }
                }

                i++;
            }

            return -1;
        }

        /// <summary>
        /// Delete the column at index
        /// </summary>
        /// <param name="index">
        /// </param>
        /// <returns>
        /// The delete.
        /// </returns>
        public bool Delete(int index)
        {
            if (index < 0 || index >= this.ColCount)
            {
                return false;
            }

            this.Columns.RemoveAt(index);
            return true;
        }

        /// <summary>
        /// Delete nb columns from index
        /// </summary>
        /// <param name="index">
        /// </param>
        /// <param name="nb">
        /// </param>
        /// <returns>
        /// The delete.
        /// </returns>
        public bool Delete(int index, int nb)
        {
            if (nb <= 0)
            {
                return false;
            }

            if (index < 0 || index >= this.ColCount)
            {
                return false;
            }

            while (index < this.ColCount && nb > 0)
            {
                this.Columns.RemoveAt(index);
                nb--;
            }

            return true;
        }

        /// <summary>
        /// Delete the row at rowIndex
        /// </summary>
        /// <param name="rowindex">
        /// </param>
        /// <returns>
        /// The delete row.
        /// </returns>
        public bool DeleteRow(int rowindex)
        {
            if (rowindex < 0 || rowindex >= this.RowCount)
            {
                return false;
            }

            foreach (var c in this.Columns)
            {
                if (c != null)
                {
                    c.Delete(rowindex);
                }
            }

            return true;
        }

        /// <summary>
        /// Delete the row at rowIndex
        /// </summary>
        /// <param name="rowindex">
        /// The rowindex.
        /// </param>
        /// <param name="nb">
        /// </param>
        /// <returns>
        /// The delete row.
        /// </returns>
        public bool DeleteRow(int rowindex, int nb)
        {
            if (nb <= 0)
            {
                return false;
            }

            for (var i = 0; i < nb; i++)
            {
                if (!this.DeleteRow(rowindex))
                {
                    return true;
                }
            }

            return true;
        }

        /// <summary>
        /// Insert column at index
        /// </summary>
        /// <param name="c">
        /// </param>
        /// <param name="index">
        /// </param>
        /// <returns>
        /// The insert.
        /// </returns>
        public bool Insert(Column c, int index)
        {
            // check index
            if (index < 0 || index >= this.ColCount)
            {
                return false;
            }

            // check column c
            if (c == null)
            {
                return false;
            }

            // if first column
            if (this.ColCount == 0)
            {
                this.Columns.Add(c);
                return true;
            }

            // check nbrow
            if (this.RowCount != c.Nb)
            {
                return false;
            }

            // no duplicate column
            var indexcol = this.ColumnIndex(c.Name);
            if (indexcol != -1)
            {
                return false;
            }

            // insert column
            this.Columns.Insert(index, c);
            return true;
        }

        /// <summary>
        /// Insert new column
        /// </summary>
        /// <param name="name">
        /// </param>
        /// <param name="index">
        /// </param>
        /// <returns>
        /// The insert.
        /// </returns>
        public bool Insert(string name, int index)
        {
            var c = new Column(name);
            return this.Insert(c, index);
        }

        #endregion

        /// <summary>
        /// The cell.
        /// </summary>
        public class Cell
        {
            #region Constructors and Destructors

            /// <summary>
            /// Initializes a new instance of the <see cref="Cell"/> class.
            /// </summary>
            /// <param name="content">
            /// The content.
            /// </param>
            /// <exception cref="Exception">
            /// </exception>
            public Cell(object content)
            {
                if (content == null)
                {
                    throw new Exception("Cell content cannot be null");
                }

                if (content.GetType() == typeof(string) || content.GetType() == typeof(ConfigurationField.Table))
                {
                    this.Content = content;
                }
                else
                {
                    throw new Exception("Cell only supports ConfigurationField.Table and string types as valid content");
                }
            }

            #endregion

            #region Properties

            /// <summary>
            /// Gets Content.
            /// </summary>
            public object Content { get; private set; }

            #endregion

            #region Operators

            /// <summary>
            /// The op_ implicit.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            public static implicit operator TimeSpan?(Cell cell)
            {
                var toTimeSpan = ConvertToTimeSpan(cell);
                return toTimeSpan;
            }

            /// <summary>
            /// The op_ implicit.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            /// <exception cref="Exception">
            /// </exception>
            public static implicit operator TimeSpan(Cell cell)
            {
                var toTimeSpan = ConvertToTimeSpan(cell);
                if (toTimeSpan.HasValue)
                {
                    return toTimeSpan.Value;
                }

                throw new FormatException(
                    string.Format("Conversion failed : {0} is not convertible to TimeSpan", cell.Content));
            }

            /// <summary>
            /// The op_ implicit.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            public static implicit operator DateTime?(Cell cell)
            {
                var toDateTime = ConvertToDateTime(cell);
                return toDateTime;
            }

            /// <summary>
            /// The op_ implicit.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            /// <exception cref="Exception">
            /// </exception>
            public static implicit operator DateTime(Cell cell)
            {
                var toDateTime = ConvertToDateTime(cell);
                if (toDateTime.HasValue)
                {
                    return toDateTime.Value;
                }

                throw new FormatException(
                    string.Format("Conversion failed : {0} is not convertible to DateTime", cell.Content));
            }

            /// <summary>
            /// The op_ implicit.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            public static implicit operator string(Cell cell)
            {
                return cell.Content as string;
            }

            /// <summary>
            /// The op_ implicit.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            public static implicit operator Table(Cell cell)
            {
                return ((ConfigurationField.Table)cell.Content).Contents as Table;
            }

            /// <summary>
            /// The op_ implicit.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            public static implicit operator int(Cell cell)
            {
                var toInt = ConvertToInt(cell);
                if (toInt.HasValue)
                {
                    return toInt.Value;
                }

                throw new FormatException(
                    string.Format("Conversion failed : {0} is not convertible to int", cell.Content));
            }

            /// <summary>
            /// The op_ implicit.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            public static implicit operator int?(Cell cell)
            {
                var toInt = ConvertToInt(cell);
                return toInt;
            }

            /// <summary>
            /// The op_ implicit.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            public static implicit operator double(Cell cell)
            {
                var toDecimal = ConvertToDecimal(cell);
                if (toDecimal.HasValue)
                {
                    return (double)toDecimal.Value;
                }

                throw new FormatException(
                    string.Format("Conversion failed : {0} is not convertible to decimal", cell.Content));
            }

            /// <summary>
            /// The op_ implicit.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            public static implicit operator double?(Cell cell)
            {
                var toDecimal = (double)ConvertToDecimal(cell);
                return toDecimal;
            }

            /// <summary>
            /// The op_ implicit.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            public static implicit operator float(Cell cell)
            {
                var toDecimal = ConvertToDecimal(cell);
                if (toDecimal.HasValue)
                {
                    return (float)toDecimal.Value;
                }

                throw new FormatException(
                    string.Format("Conversion failed : {0} is not convertible to decimal", cell.Content));
            }

            /// <summary>
            /// The op_ implicit.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            public static implicit operator float?(Cell cell)
            {
                var toDecimal = (float)ConvertToDecimal(cell);
                return toDecimal;
            }

            /// <summary>
            /// The op_ implicit.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            public static implicit operator bool(Cell cell)
            {
                var toBool = ConvertToBool(cell);
                if (toBool.HasValue)
                {
                    return toBool.Value;
                }

                throw new FormatException(
                    string.Format("Conversion failed : {0} is not convertible to bool", cell.Content));
            }

            /// <summary>
            /// The op_ implicit.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            public static implicit operator bool?(Cell cell)
            {
                var toBool = ConvertToBool(cell);
                return toBool;
            }

            #endregion

            #region Methods

            /// <summary>
            /// The convert to bool.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            private static bool? ConvertToBool(Cell cell)
            {
                bool? toBool = new bool();
                try
                {
                    toBool = bool.Parse(cell.Content as string);
                }
                catch
                {
                }

                return toBool;
            }

            /// <summary>
            /// The convert to date time.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            private static DateTime? ConvertToDateTime(Cell cell)
            {
                DateTime? toDateTime = new DateTime();

                try
                {
                    toDateTime = DateTime.Parse(cell.Content as string);
                }
                catch
                {
                }

                return toDateTime;
            }

            /// <summary>
            /// The convert to decimal.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            private static decimal? ConvertToDecimal(Cell cell)
            {
                decimal factor = 1;
                var content = cell.Content as string;
                if (content.EndsWith("%"))
                {
                    factor = 100;
                    content = content.TrimEnd(new[] { '%' });
                }

                decimal? toDecimal = new decimal();
                try
                {
                    toDecimal = decimal.Parse(content, Constants.CultureInfo) / factor;
                }
                catch
                {
                }

                return toDecimal;
            }

            /// <summary>
            /// The convert to int.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            private static int? ConvertToInt(Cell cell)
            {
                var content = cell.Content as string;
                int? toInt = new int();
                try
                {
                    toInt = int.Parse(content, Constants.CultureInfo);
                }
                catch
                {
                }

                return toInt;
            }

            /// <summary>
            /// The convert to time span.
            /// </summary>
            /// <param name="cell">
            /// The cell.
            /// </param>
            /// <returns>
            /// </returns>
            private static TimeSpan? ConvertToTimeSpan(Cell cell)
            {
                TimeSpan? toTimeSpan = new TimeSpan();

                try
                {
                    var times = (cell.Content as string).Split(new[] { ':' });
                    if (times.Length != 3)
                    {
                        return toTimeSpan;
                    }

                    var hours = int.Parse(times[0]);
                    var minutes = int.Parse(times[1]);
                    var seconds = int.Parse(times[2]);

                    toTimeSpan = new TimeSpan(hours, minutes, seconds);
                }
                catch
                {
                }

                return toTimeSpan;
            }

            #endregion
        }

        /// <summary>
        /// The column.
        /// </summary>
        [Serializable]
        public class Column
        {
            #region Constants and Fields

            /// <summary>
            /// Columns
            /// </summary>
            private readonly IList<Cell> Data = new List<Cell>();

            #endregion

            #region Constructors and Destructors

            /// <summary>
            /// Initializes a new instance of the <see cref="Column"/> class. 
            /// Constructor
            /// </summary>
            /// <param name="name">
            /// not null
            /// </param>
            public Column(string name)
            {
                this.Name = name;
            }

            #endregion

            #region Properties

            /// <summary>
            /// Gets Name.
            /// </summary>
            public string Name { get; private set; }

            /// <summary>
            /// Nb data
            /// </summary>
            public int Nb
            {
                get
                {
                    return this.Data.Count;
                }
            }

            #endregion

            #region Indexers

            /// <summary>
            /// Gets data at index
            /// </summary>
            /// <param name="index"></param>
            /// <returns></returns>
            public Cell this[int index]
            {
                get
                {
                    if (index >= 0 && index < this.Nb)
                    {
                        return this.Data[index];
                    }

                    return null;
                }

                set
                {
                    if (index >= 0 && index < this.Nb)
                    {
                        this.Data[index] = value;
                    }
                }
            }

            #endregion

            #region Public Methods

            /// <summary>
            /// Add data
            /// </summary>
            /// <param name="o">
            /// not null
            /// </param>
            /// <returns>
            /// The add.
            /// </returns>
            public bool Add(Cell o)
            {
                if (o == null)
                {
                    return false;
                }

                this.Data.Add(o);
                return true;
            }

            /// <summary>
            /// Add data
            /// </summary>
            /// <param name="o">
            /// not null
            /// </param>
            /// <param name="nb">
            /// not null
            /// </param>
            /// <returns>
            /// The add.
            /// </returns>
            public bool Add(Cell o, int nb)
            {
                if (nb <= 0)
                {
                    return false;
                }

                if (o == null)
                {
                    return false;
                }

                for (var i = 0; i < nb; i++)
                {
                    this.Data.Add(o);
                }

                return true;
            }

            /// <summary>
            /// Clear the column by removing all data from the list
            /// </summary>
            public void Clear()
            {
                this.Data.Clear();
            }

            /// <summary>
            /// Delete value at index
            /// </summary>
            /// <param name="index">
            /// </param>
            /// <returns>
            /// The delete.
            /// </returns>
            public bool Delete(int index)
            {
                if (index < 0 || index >= this.Nb)
                {
                    return false;
                }

                this.Data.RemoveAt(index);
                return true;
            }

            /// <summary>
            /// Delete nb value from index
            /// </summary>
            /// <param name="index">
            /// </param>
            /// <param name="nb">
            /// The nb.
            /// </param>
            /// <returns>
            /// The delete.
            /// </returns>
            public bool Delete(int index, int nb)
            {
                if (nb <= 0)
                {
                    return false;
                }

                if (index < 0 || index >= this.Nb)
                {
                    return false;
                }

                while (index < this.Nb && nb > 0)
                {
                    this.Data.RemoveAt(index);
                    nb--;
                }

                return true;
            }

            /// <summary>
            /// insert object
            /// </summary>
            /// <param name="index">
            /// not null
            /// </param>
            /// <param name="o">
            /// not null
            /// </param>
            /// <returns>
            /// The insert.
            /// </returns>
            public bool Insert(int index, Cell o)
            {
                if (index < 0 || index >= this.Nb)
                {
                    return false;
                }

                if (o == null)
                {
                    return false;
                }

                this.Data.Insert(index, o);
                return true;
            }

            /// <summary>
            /// insert object
            /// </summary>
            /// <param name="index">
            /// not null
            /// </param>
            /// <param name="o">
            /// not null
            /// </param>
            /// <param name="nb">
            /// not null
            /// </param>
            /// <returns>
            /// The insert.
            /// </returns>
            public bool Insert(int index, Cell o, int nb)
            {
                if (index < 0 || index >= this.Nb || nb <= 0)
                {
                    return false;
                }

                if (o == null)
                {
                    return false;
                }

                for (var i = 0; i < nb; i++)
                {
                    this.Data.Insert(index, o);
                }

                return true;
            }

            /// <summary>
            /// Print the column
            /// </summary>
            public void Print()
            {
                if (this.Name != null)
                {
                    Stdout.WriteLine(this.Name);
                }
                else
                {
                    Stdout.WriteLine("Invalid Name");
                }

                foreach (var o in this.Data)
                {
                    Stdout.WriteLine(o);
                }
            }

            /// <summary>
            /// Gets Item as String at index 
            /// </summary>
            /// <param name="index">
            /// </param>
            /// <returns>
            /// The string item.
            /// </returns>
            public string StringItem(int index)
            {
                return this[index].ToString();
            }

            /// <summary>
            /// Update value at index
            /// </summary>
            /// <param name="index">
            /// not null
            /// </param>
            /// <param name="o">
            /// not null
            /// </param>
            /// <returns>
            /// The update.
            /// </returns>
            public bool Update(int index, Cell o)
            {
                if (index < 0 || index >= this.Nb)
                {
                    return false;
                }

                if (o == null)
                {
                    return false;
                }

                this[index] = o;
                return true;
            }

            #endregion
        }
    }
}